R/financial functions.R

Defines functions fin_ticker fin_JustifiedPE plot.percent print.percent fin_percent bucks fin_NI fin_interest fin_expected fin_net_present_value fin_valuation

Documented in bucks fin_expected fin_interest fin_JustifiedPE fin_NI fin_percent fin_ticker fin_valuation plot.percent print.percent

# =======================
# = Financial utilities =
# =======================

#' Work the valuation of a company
#'
#' @description
#' `fin_valuation` uses the revenue, operating margin, expenses and PE to compute a market capitalization.
#' Better to use a more powerful online site.
#'
#' @details
#' Revenue is multiplied by opmargin to get a gross profit. From this the proportion specified in `expenses` is subtracted 
#' and the resulting earnings turned into a price via the `PE`
#' 
#' @param revenue Revenue of the company
#' @param opmargin Margin on operating revenue
#' @param expenses Additional fixed costs
#' @param PE of the company
#' @param symbol Currency
#' @param use reporting values in "B" (billion) or "M" (millions)
#' @return - value
#' @export
#' @family Miscellaneous Functions
#' @seealso - [fin_interest()], [fin_NI()], [fin_percent()]
#' @md
#' @examples
#' fin_valuation(rev=7e9, opmargin=.1, PE=33)
#' # Market cap =  $18,480,000,000
#' # (Based on PE= 33, operating Income of $0.70 B, and net income =$0.56B
#'
fin_valuation <- function(revenue=6e6*30e3, opmargin=.08, expenses=.2, PE=30, symbol = "$", use = c("B", "M")) {
	use = match.arg(use)
	if(use=="B"){
		divisor=1e9
	} else {
		divisor=1e6
	}
	operatingIncome = revenue * opmargin
	netIncome = operatingIncome *(1-expenses)
	marketCap = netIncome*PE
	class(marketCap) = 'money'; attr(marketCap, 'symbol') = symbol
	class(netIncome) = 'money'; attr(netIncome, 'symbol') = symbol
	class(operatingIncome) = 'money'; attr(operatingIncome, 'symbol') = symbol
	
	cat("Market cap = ", print(marketCap, cat=F))
	cat("\n(Based on PE= ", PE, ", operating Income of ", print(operatingIncome/divisor, cat=F), " ", use, ", and net income =", print(netIncome/divisor, cat=F), use, "\n", sep = "")

	invisible(marketCap)
}

#' Compute the net present value of a future income stream
#'
#' @description
#' `fin_valuation` uses the revenue, operating margin, expenses and PE to compute a market capitalization.
#' Better to use a more powerful online site.
#'
#' @details
#' Revenue stream is discounted back to a present day cash amount which is equivalent.
#' 
#' @param cashflow Value of expected recurring payment
#' @param discount Percent return to discount against (.05 = 5%)
#' @param periods How many periods the stream delivers, e.g., (90-65) for 25 of a pension.
#' @param PE of the company
#' @param symbol Currency
#' @param use reporting values in "B" (billion) or "M" (millions)
#' @return - value
#' @export
#' @family Miscellaneous Functions
#' @seealso - [fin_interest()], [fin_NI()], [fin_percent()]
#' @md
#' @examples
#' fin_net_present_value(27e3, .05, 25)
#'
fin_net_present_value <- function(income=27e3, discount_rate=.05, periods = 25, symbol = NULL) {
	if(is.null(symbol)){symbol = umx_set_dollar_symbol(silent=TRUE)}
	
	cashflows   = rep(income, periods)
	timePeriods = seq(1, periods)
	discount_factors = 1/(1+discount_rate)^timePeriods
	present_values = cashflows*discount_factors
	pv = sum(present_values)
	cat("\nBased on a discount rate of ", discount_rate*100, "%, an income of ", bucks(income, symbol, cat=TRUE), " for ", periods, " years, has a net present value of \n", sep="")
	cat("\n", bucks(pv, symbol))

	invisible(pv)
}

#' Compute the future value and gain of an investment
#'
#' @description
#' fin_expected takes a current and fair value, as well as a cost of capital, and returns the expected gain.
#'
#' @param current The current market value of the instrument
#' @param fair The user's estimated fair value.
#' @param capital The cost of capital (defaults to .15)
#' @return - expected gain
#' @export
#' @family Miscellaneous Functions
#' @seealso - [fin_interest()]
#' @md
#' @examples
#' fin_expected(15, 45)
fin_expected <- function(current, fair, capital=.15) {
	delta  = (fair-current)      ; cat("delta (fair-current)= $", delta, "\n")
	growth = fair*capital        ; cat("growth = $", growth, "\n")
	expectedGain = (growth+delta); cat("expected gain = $", expectedGain, "\n")
	final  = fair*(1+capital)    ; cat("future value (final) = $", final, "\n")
	cat("return = ", round(((final/current)-1)*100, 2), "%\n")
	return(expectedGain)
}


#' Compute the value of a principal & annual deposits at a compound interest over a number of years
#' @description
#' Allows you to determine the final value of an initial `principal` (with optional 
#' periodic `deposits`), over a number of years (`yrs`) at a given rate of `interest`.
#' Principal and deposits are optional. You control compounding periods each year (n) and whether deposits occur at the beginning or end of the year.
#' The function outputs a nice table of annual returns, formats the total using a user-settable currency `symbol`. Can also `report` using a web table.
#' 
#' *notes*: Graham valuation: fair P/E = 9 + (1.5 * growth%). e.g.  $INTEL fair P/E = 9+.5*3 = 10.5 up to  9+2*10 = 29
#' Can move the weighting between a conservative .5 and an optimistic 2 (in terms of how long the growth will last and how low the hurdle rate is)
#' 
#' 
#' @param principal The initial investment at time 0 (default 100)
#' @param deposits Optional periodic additional investment each *year*.
#' @param interest Annual interest rate (default .05)
#' @param inflate How much to inflate deposits over time (default 0)
#' @param yrs Duration of the investment (default 10).
#' @param n Compounding intervals per year (default 12 (monthly), use 365 for daily)
#' @param when Deposits made at the "beginning" (of each year) or "end"
#' @param symbol Currency symbol to embed in the result.
#' @param report "markdown" or "html", 
#' @param table Whether to print a table of annual returns (default TRUE)
#' @param largest_with_cents Default = 0
#' @param baseYear Default = current year (for table row labels)
#' @param final if set (default = NULL), returns the rate required to turn principal into final after yrs (principal defaults to 1)
#' @return - Value of balance after yrs of investment.
#' @export
#' @family Miscellaneous Functions
#' @seealso - [umx_set_dollar_symbol()], [fin_percent()], [fin_NI()], [fin_valuation()]
#' @references - <https://en.wikipedia.org/wiki/Compound_interest>
#' @md
#' @examples
#' \dontrun{
#' # 1. Value of a principal after yrs years at 5% return, compounding monthly.
#' # Report in browser as a nice table of annual returns and formatted totals.
#' fin_interest(principal = 5000, interest = 0.05, rep= "html")
#' }
#'
#' # Report as a nice markdown table
#' fin_interest(principal = 5000, interest = 0.05, yrs = 10)
#'
#' umx_set_dollar_symbol("£")
#' # 2 What rate is needed to increase principal to final value in yrs time?
#' fin_interest(1, final = 1.4, yrs=5)
#' fin_interest(principal = 50, final=200, yrs = 5)
#'
#' # 3. What's the value of deposits of $100/yr after 10 years at 7% return?
#' fin_interest(0, deposits = 100, interest = 0.07, yrs = 10, n = 12)
#'
#' # 4. What's the value of $20k + $100/yr over 10 years at 7% return?
#' fin_interest(principal= 20e3, deposits= 100, interest= .07, yrs= 10, symbol="$")
#'
#' # 5. What is $10,000 invested at the end of each year for 5 years at 6%?
#' fin_interest(deposits = 10e3, interest = 0.06, yrs = 5, n=1, when= "end")
#'
#' # 6. What will $20k be worth after 10 years at 15% annually (n=1)?
#' fin_interest(deposits=20e3, interest = 0.15, yrs = 10, n=1, baseYear=1)
#' # $466,986
#'
#' # manual equivalent
#' sum(20e3*(1.15^(10:1))) # 466985.5
#'
#' # 7. Annual (rather than monthly) compounding (n=1)
#' fin_interest(deposits = 100, interest = 0.07, yrs = 10, n=1)
#' 
#' # 8 Interest needed to increase principal to final value in yrs time.
#' fin_interest(principal = 100, final=200, yrs = 5)
#'
fin_interest <- function(principal = 100, deposits = 0, inflate = 0, interest = 0.05, yrs = 10, final= NULL, n = 12, when = "beginning", symbol = NULL, largest_with_cents = 0, baseYear= as.numeric(format(Sys.time(), "%Y")), table = TRUE, report= c("markdown", "html")){
	report = match.arg(report)
	if(is.null(symbol)){symbol = umx_set_dollar_symbol(silent=TRUE)}
	if(principal==0){
		caption= paste0("Compounding ", bucks(deposits, symbol, cat=TRUE), " deposits over ", yrs, " years at ", interest*100, "% interest with ", inflate*100, "% inflation.")
	} else {
		caption= paste0("Compounding ", bucks(principal, symbol, cat=TRUE), " principle plus ", bucks(deposits, symbol, cat=TRUE), " annual deposits, ", interest * 100, "% interest and ", inflate*100, "% inflation.")
	}

	if(inflate != 0){
		deposits = c(deposits, rep(deposits, times = yrs-1) *(1+inflate)^c(1:(yrs-1)))
	}else{
		deposits = rep(deposits, times = yrs)
	}
	if(!is.null(final)){
		# final = prin*(1+rate)^y
		if(principal==0){ principal=1 }
		return((final/principal)^(1/(yrs+1))-1)
		# rate is the years root of (final *prin?)
	}

	# 1. compute compounding rate per unit time n (allowing for zero interest so 1.0)
	rate = ifelse(interest==0, 1, 1+(interest/n))

	tableOut = data.frame(Year = NA, Deposits = NA, Interest = NA, Total_Deposits = NA, Total_Interest = NA, Total = scales::dollar(principal, prefix = symbol, largest_with_cents = 0))
	balance  = principal
	totalDeposits = 0
	totalInterest = 0
	for (yr in 1:yrs) {
		# 1. Compute compounding rate per unit time n (allowing for zero interest so 1.0)
		if(when == "beginning"){
			# Deposits at the beginning of each year
			thisInterest = ((balance + deposits[yr]) * rate^n) - (balance + deposits[yr])
		} else {
			# Deposits at the end of the year
			thisInterest = (balance * rate^n) - balance
		}
		totalDeposits = (totalDeposits + deposits[yr])
		totalInterest = (totalInterest + thisInterest)
		balance       = (balance + deposits[yr] + thisInterest)
		thisRow = c(Year=yr+baseYear, Deposit= deposits[yr], Interest = thisInterest, Total_Deposit = totalDeposits, Total_Interest = totalInterest, Total = balance)
		thisRow = c(thisRow[1], scales::dollar(thisRow[-1], prefix = symbol, largest_with_cents = largest_with_cents))
		tableOut = rbind(tableOut, thisRow)
	}
	if(table){
		# principal = 0, deposits = 0, inflate = 0, interest = 0.05, yrs
		umx_print(tableOut, justify = "right", caption = caption, report=report)
	}

	if(length(deposits)==1){
		# 2. compute compounded value of the principal (initial deposit)
		Compound_interest_for_principal = principal* rate^(n*yrs)

		# 3. compute compounded value of the deposits

		if(interest==0){
			Future_value_of_a_series = deposits * yrs
		} else {
			# beginning: A = PMT * (((1 + r/n)^(nt) - 1) / (r/n))
			# end      : A = PMT * (((1 + r/n)^(nt) - 1) / (r/n)) * (1+r/n)
			if(when == "beginning"){
				# deposits at the beginning of each year
				periods = (yrs:1)*n
				Future_value_of_a_series = sum(deposits*(rate^periods))
			} else {
				# deposits at the end of the year
				periods = ((yrs-1):1)*n
				Future_value_of_a_series = sum(deposits*(rate^periods)) + (1*deposits)
			}
		}

		Total =  Compound_interest_for_principal+ Future_value_of_a_series
	} else {
		Total = balance
	}
	class(Total) = 'money'
	attr(Total, 'symbol') = symbol
	return(Total)
}


#' Compute NI given annual Earnings.
#'
#' @description
#' Employees pay contributions at 12%% on annual earnings between £9,568 and £50,270. Above that you pay at 2%%. 
#' Employers pay at 13.8%% on all annual earnings of more than £8,840, although there are different thresholds 
#' for those under the age of 21 and for apprentices under the age of 25.
#'
#' @param annualEarnings Employee annual earnings.
#' @param symbol Currency symbol to embed in the result.
#' @return - NI
#' @export
#' @family Miscellaneous Functions
#' @seealso - [fin_interest()], [fin_percent()], [fin_valuation()]
#' @references - <https://www.telegraph.co.uk/tax/tax-hacks/politicians-running-scared-long-overdue-national-insurance-overhaul/>
#' @md
#' @examples
#' fin_NI(42e3)
#' fin_NI(142000)
#'
fin_NI <- function(annualEarnings, symbol = "\u00A3") {
	if(annualEarnings < 50270){
		employee = .12 * max(0, (annualEarnings- 9568))
	} else {
		employee = (.12 * (annualEarnings- 9568)) + (.02 * (annualEarnings-50270))
	}
	employer = .138 * max((annualEarnings - 8840), 0)

	Total = employer + employee
	class(Total) = 'money'
	attr(Total, 'symbol') = symbol
	cat(paste0("Employer pays ", bucks(employer, symbol = symbol, cat = FALSE), ", and employee pays ", bucks(employee, symbol = symbol, cat=FALSE),
	 ". So ", round((employer+employee)/annualEarnings*100, 2),	" % total!\n")
	 )
	return(Total)
}

#' Print a money object
#'
#' @description Print function for "money" objects, e.g. [fin_interest()].
#'
#' @aliases bucks print
#' @param x money object.
#' @param symbol Default prefix if not set.
#' @param big.mark option defaulting to ","
#' @param decimal.mark option defaulting to "."
#' @param trim option defaulting to TRUE
#' @param largest_with_cents option defaulting to 1e+05
#' @param negative_parens option defaulting to "hyphen"
#' @param ... further arguments passed to or from other methods. also cat =F to return string
#' @return - invisible
#' @seealso - [umx::fin_percent()], [umx::fin_interest()], [scales::dollar()]
#' @md
# #' @family print
#' @export
#' @examples
#' bucks(100 * 1.05^32)
#' fin_interest(deposits = 20e3, interest = 0.07, yrs = 20)
#'
bucks <- function(x, symbol = "$", big.mark = ",", decimal.mark = ".", trim = TRUE, largest_with_cents = 1e+05, negative_parens = c("hyphen", "minus", "parens"), ...) {
	dot.items = list(...) # grab all the dot items cat
	cat = ifelse(is.null(dot.items[["cat"]]), TRUE, dot.items[["cat"]])
	if(is.null(dot.items[["cat"]])){
		cat = TRUE
	} else {
		cat = FALSE
		dot.items[["cat"]] = NULL
	}

	if(!is.null(attr(x, 'symbol')) ){
		symbol = attr(x, 'symbol')
	}
	formatted = scales::dollar(as.numeric(x), prefix = symbol, big.mark = big.mark, decimal.mark = decimal.mark, trim =trim, largest_with_cents = largest_with_cents, style_negative = negative_parens, ...)
	if(cat){
		cat(formatted)
	} else {
		formatted
	}
}

#' @export
#' @method print money
print.money <- bucks

#' Compute the percent change needed to return to the original value after percent off (or on).
#'
#' @description
#' Determine the percent change needed to "undo" an initial percent change. Has a plot function as well.
#' If an amount of $100 has 20% added, what percent do we need to drop it by to return to the original value?
#' 
#' `fin_percent(20)` yields $100 increased by 20% = $120 (Percent to reverse = -17%)
#' 
#' @param percent Change in percent (enter 10 for 10%, not 0.1)
#' @param value Principal
#' @param symbol value units (default = "$")
#' @param digits Rounding of results (default 2 places)
#' @param plot Whether to plot the result (default TRUE)
#' @param logY Whether to plot y axis as log (TRUE)
#' @return - new value and change required to return to baseline.
#' @export
#' @family Miscellaneous Functions
#' @seealso - [fin_interest()]
#' @md
#' @examples
#' # Percent needed to return to original value after 10% taken off
#' fin_percent(-10)
#'
#' # Percent needed to return to original value after 10% added on
#' fin_percent(10)
#'
#' # Percent needed to return to original value after 50% off 34.50
#' fin_percent(-50, value = 34.5)
fin_percent <- function(percent, value= 100, symbol = "$", digits = 2, plot = TRUE, logY = TRUE) {
	percent  = percent/100
	newValue = value * (1 + percent)
	percent_to_reverse = (value/newValue) - 1
	class(newValue) = 'percent'
	attr(newValue, 'oldValue') = value
	attr(newValue, 'percent')  = percent
	attr(newValue, 'digits')   = digits
	attr(newValue, 'symbol')   = symbol
	attr(newValue, 'percent_to_reverse') = percent_to_reverse

	if(plot){
		plot(newValue, logY = logY)
	}else{
		return(newValue)
	}
}


#' Print a percent object
#'
#' Print method for "percent" objects: e.g. [umx::fin_percent()]. 
#'
#' @param x percent object.
#' @param ... further arguments passed to or from other methods.
#' @return - invisible
#' @seealso - [umx::fin_percent()]
#' @md
#' @method print percent
#' @export
#' @examples
#' # Percent needed to return to original value after 10% off
#' fin_percent(-10)
#' # Percent needed to return to original value after 10% on
#' fin_percent(10)
#'
#' # Percent needed to return to original value after 50% off 34.50
#' fin_percent(-50, value = 34.5)
#'
print.percent <- function(x, ...) {
	if(!is.null(attr(x, 'digits')) ){
		digits = attr(x, 'digits')
	}
	oldValue = round(attr(x, 'oldValue'), digits)
	percentChange  = attr(x, 'percent')
	symbol   = attr(x, 'symbol')
	percent_to_reverse = round(attr(x, 'percent_to_reverse'), digits)
	dir = ifelse(percentChange < 0, "decreased", "increased")

	cat(symbol, oldValue, " ", dir , " by ", percentChange*100, "% = ", symbol, x, " (Percent to reverse = ", percent_to_reverse*100, "%)", sep="")
}

#' Plot a percent change graph
#'
#' Plot method for "percent" objects: e.g. [umx::fin_percent()]. 
#'
#' @param x percent object.
#' @param ... further arguments passed to or from other methods.
#' @return - invisible
#' @seealso - [umx::fin_percent()]
#' @md
#' @method plot percent
#' @export
#' @examples
#' # Percent needed to return to original value after 10% off
#' fin_percent(-10)
#' # Percent needed to return to original value after 10% on
#' tmp = fin_percent(10)
#' plot(tmp)
#'
#' # Percent needed to return to original value after 50% off 34.50
#' fin_percent(-50, value = 34.5, logY = FALSE)
#'
plot.percent <- function(x, ...) {
	tmp = list(...) # pull logY if passed in
	logY = tmp$logY
	symbol   = attr(x, 'symbol')
	digits   = attr(x, 'digits')
	oldValue = round(attr(x, 'oldValue'), digits)
	percentChange  = attr(x, 'percent')	
	percent_to_reverse = round(attr(x, 'percent_to_reverse'), digits)
	dir = ifelse(percentChange < 0, "decreased", "increased")
	# fnReversePercent(-.1)
	fnReversePercent <- function(x) {
		# 1/(1+.1)
		percentOn = x/100
		newValue = (1 + percentOn)
		percent_to_reverse = 1-(1/newValue)
		return(-percent_to_reverse*100)
	}
	# x range	= -100 (%) to +500 (%)?
	# y = -100 to +200?
	# y range	= -100 to +200?
	if(percentChange>0){
		p = ggplot(data.frame(x = c(0, 90)), aes(x))
		lab = paste0(round(percentChange*100, 2), "% on = ", round(percent_to_reverse * 100, 2), "% off", sep = "")
		labXpos = 50
		labYpos = -20
		logY = FALSE
	} else {
		p = ggplot(data.frame(x = c(-90, 0)), aes(x))
		lab = paste0(round(percentChange*100, 2), "% off = ", round(percent_to_reverse * 100, 2), "% on", sep = "")
		labXpos = -50
		labYpos = 700
	}
	if(is.null(logY)||!(logY)){
		p = p + ggplot2::scale_y_continuous(n.breaks = 8) + ggplot2::scale_x_continuous(n.breaks = 10)
		p = p + cowplot::draw_label(lab, vjust = 1, hjust = .5, x = labXpos, y = labYpos, color= "grey")
		# hor & vert
		p = p + ggplot2::geom_segment(x = percentChange*100, xend=-100, y=percent_to_reverse*100, yend=percent_to_reverse*100, alpha=.5, color = "lightgrey")
		p = p + ggplot2::geom_segment(x = percentChange*100, xend=percentChange*100, y=-10, yend=percent_to_reverse*100, alpha=.5, color = "lightgrey")
	} else {
		p = p + ggplot2::scale_y_continuous(n.breaks = 8, trans="log10") + ggplot2::scale_x_continuous(n.breaks = 10) 
		p = p + cowplot::draw_label(lab, vjust = 1, hjust = .5, x = labXpos, y = log10(labYpos), color= "grey")
		# hor & vert
		p = p + ggplot2::geom_segment(x = percentChange*100, xend=-100             , y= log10(percent_to_reverse*100), yend= log10(percent_to_reverse*100), alpha=.5, color = "lightgrey")
		p = p + ggplot2::geom_segment(x = percentChange*100, xend=percentChange*100, y= -10, yend= log10(percent_to_reverse*100), alpha= .5, color = "lightgrey")
	}
	p = p + ggplot2::stat_function(fun = fnReversePercent, color= "lightblue")
	p = p + labs(x = "Percent change", y = "Percent change to reverse", title = paste0(percentChange*100, " percent ", ifelse(percentChange>0, "on ", "off "), oldValue, " = ", (1+percentChange)*oldValue))
	# p = p + ggplot2::geom_area() can't do with stat fun ...

	# p = p + cowplot::draw_label("\u2B55", hjust=0, vjust=1, x = percentChange*100, y = percent_to_reverse*100, color = "lightblue")

	if(umx_set_plot_use_hrbrthemes(silent = TRUE)){
		# p = p + hrbrthemes::theme_ipsum()
		p = p + hrbrthemes::theme_ft_rc()
	} else {
		# p = p + ggplot2::theme_bw()
		p = p + cowplot::theme_cowplot(font_size = 11)
	}

	
	print(p)
	cat(symbol, oldValue, " ", dir , " by ", percentChange*100, "% = ", symbol, x, " (Percent to reverse = ", percent_to_reverse*100, "%)", sep="")
	invisible(p)
}

#' Justified P/E Ratio
#'
#' @description
#' Compute the Justified P/E of a stock.
#' Justified P/E = ( (DPS / EPS) * (1 + g)) / (k – g)
#' DPS is the dividend per share, EPS is the earnings per share,
#' g is the sustainable growth rate, and k is the required rate of return.
#' @param Dividend The dividend.
#' @param EPS The Earnings per Share.
#' @param growthRate The growth rate.
#' @param discountRate Your chosen discount rate.
#' @param basePE The base PE.
#' @param yrs Years.
#' @return - A PE that is justified for this stock.
#' @export
#' @family Miscellaneous Functions
#' @seealso - [fin_interest()], [fin_percent()], [fin_NI()]
#' @md
#' @examples
#' # fin_JustifiedPE(Dividend= .8, EPS = 2, growthRate = .06, discountRate = .1)
#' 
fin_JustifiedPE <- function(Dividend= .02, EPS = 1, growthRate = .08, discountRate = .12, basePE= 20, yrs=10) {
	paste0("Based on growth (", growthRate*100, "% expected growth for ", yrs, " years and a base P/E of ",
	basePE, "), the justified P/E would be: ", (growthRate * yrs) + basePE )
	
   # ((0.4 * 2) * (1 + 0.06)) / (0.1 - 0.06)
   # ((Dividend/EPS) * (1 + growthRate)) / (k-growthRate)
   # Justified P/E Ratio = 16.8
}

#' Open a ticker in yahoo finance.
#'
#' @description
#' Open a stock ticker, currently in yahoo finance
#'
#' @param ticker A stock symbol to look up, e.g., "OXY"
#' @return - Open a ticker in a finance site online
#' @export
#' @family Miscellaneous Functions
#' @seealso - [fin_interest()], [fin_percent()], [fin_NI()]
#' @md
#' @examples
#' # Open $INTC in yahoo finance.
#' \dontrun{
#' fin_ticker("INTC")
#' }

fin_ticker <- function(ticker= "INTC") {
	url =paste0("https://finance.yahoo.com/quote/", ticker)
	# https://www.google.com/finance/quote/SWBI:NASDAQ
	browseURL(url, browser = getOption("browser"))
}
tbates/umx documentation built on April 13, 2025, 7:22 a.m.